#include <ximera/system/application.hpp>
#include <ximera/logger.hpp>

#include "boxentity.hpp"

#include <iostream>

using namespace ximera;

struct PhysicsTestHandler : ximera::system::AppHandler
{
    PhysicsTestHandler(system::Application& app)
        : application(app) {}

    virtual void onStart(system::AppEventArgs& event)
    {
        try
        {
            setupInput();
            setupWorld();
            setupCamera();
            setupLights();
            setupEntities();
            setupPhysics();
        }
        catch (ximera::Exception& e)
        {
            event.app.shouldClose(true);
            XIMERA_LOG() << e << std::endl;
        }
    }

    virtual void onUpdate(system::UpdateEventArgs& event) 
    {
        physicsWorld->step(event.secs);

        for_each(boxes.begin(), boxes.end(), boost::bind(&BoxEntity::update, _1));        

        math::Vector3 moveDir;

        if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::W))
            moveDir = camera->direction();
        else if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::S))
            moveDir = -camera->direction();

        if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::A))
            moveDir += -camera->right();
        else if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::D))
            moveDir += camera->right();

        if (moveDir != math::Vector3::ZERO)
        {
            Real const velocity = 40.0f;
            camera->position(camera->position() + (moveDir * (velocity * event.secs)));
        }

        Real rotation = -event.app.inputSystem()->mouse()->axisRel(1);

        if (!math::util::equal(rotation, 0))
        {
            Real const velocity = 40.05f;
            rotation = rotation * velocity;

            camera->pitch(rotation);
        }

        rotation = -event.app.inputSystem()->mouse()->axisRel(0);

        if (!math::util::equal(rotation, 0))
        {
            Real const velocity = 35.0f;
            rotation = rotation * velocity;

            camera->yaw(rotation);
        }
    }

private:
    system::Application& application;

    graphics::IWorldPtr graphicsWorld;
    graphics::ICameraPtr camera;
    graphics::IViewportPtr viewport;
    graphics::ILightPtr light;

    SharedPtr<physics::IWorld> physicsWorld;
    graphics::IEntityPtr floor;
    graphics::ISceneNode* floorNode;
    physics::IEntity* floorBody;

    typedef std::vector<BoxEntity> BoxContainer;
    BoxContainer boxes;

private:
    void setupInput()
    {
    }

    void setupWorld()
    {
        graphicsWorld.reset(application.graphicsSystem()->createWorld());
        application.graphicsSystem()->ambientLight(math::Color(0.5f, 0.5f, 0.5f));        

        physicsWorld.reset(application.physicsSystem()->createWorld());
        physicsWorld->gravity(math::Vector3(0, -4.8f, 0));
        physicsWorld->stepType(physics::StepType::Normal);
    }

    void setupLights()
    {
        light.reset(graphicsWorld->createLight(L"light"));
        light->position(math::Vector3(20, 80, 50));
        light->diffuseColor(math::Color(10, 110, 0, 255));
        light->type(graphics::LightType::Directional);
    }

    void setupCamera()
    {
        camera.reset(graphicsWorld->createCamera(L"default"));
        camera->position(math::Vector3(70, 20, -50));
        camera->lookAt(math::Vector3(0, 0, 0));

        viewport.reset(graphicsWorld->createViewport(camera.get()));
    }

    void setupEntities()
    {
        floor.reset(graphicsWorld->createEntity(L"floor", L"cube.mesh"));
        floor->materialName(L"RustyBarrel");
        floorNode = graphicsWorld->rootNode()->createChild(L"floor");
        floorNode->attach(floor.get());
        floorNode->position(math::Vector3(0, 0, 0));
        floorNode->scale(8, 0.005f, 8);
    }

    void setupPhysics()
    {
        /*floorBody = physicsWorld->space()->createEntity();
        floorBody->body()->position(math::Vector3(0, -198, 0));
        floorBody->body()->mass(35);
        floorBody->body()->setFlag(physics::BodyFlag::NoResponse);
        floorBody->body()->setFlag(physics::BodyFlag::NoGravity);
        physics::IBox* box = floorBody->createShape<physics::IBox>();
        box->extents(math::Vector3(40, 0.5f, 40));*/

        floorBody = physicsWorld->space()->createEntity(physics::EntityType::Static);
        physics::IPlane* plane = floorBody->createShape<physics::IPlane>();
        plane->plane(math::Plane(math::Vector3(0, 1, 0), 0));

        for (int i = 0; i < 5; ++i)
            boxes.push_back(BoxEntity(graphicsWorld.get(), physicsWorld.get()));
    }
};

int main()
{
    try
    {
        XIMERA_LOG().stream(&std::wcout);

        system::AppSettings settings;
        settings[L"ximera.plugins.input"] = XIMERA_PLUGIN(L"XimeraDInput8");
        settings[L"ximera.plugins.graphics"] = XIMERA_PLUGIN(L"XimeraOgre");
        settings[L"ximera.plugins.physics"] = XIMERA_PLUGIN(L"XimeraODE");
        settings[L"ximera.ogre.showdialog"] = L"1";

        system::Application app(settings);

        PhysicsTestHandler handler(app);
        app.addHandler(handler);

        return app.run();
    }
    catch (ximera::Exception& e)
    {
        XIMERA_LOG() << e << std::endl;
    }
}
